package com.shangyueshang.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.shangyueshang.enums.OrderType;
import com.shangyueshang.mapper.OrderMapper;
import com.shangyueshang.mapper.ProfitExecuteLogMapper;
import com.shangyueshang.mapper.RevenueMapper;
import com.shangyueshang.model.*;
import com.shangyueshang.request.AccountingDTO;
import com.shangyueshang.request.PageReq;
import com.shangyueshang.response.HttpResult;
import com.shangyueshang.response.KeyValDecimal;
import com.shangyueshang.response.PageResult;
import com.shangyueshang.response.ShopSkuDTO;
import com.shangyueshang.service.GoodsRevenueService;
import com.shangyueshang.service.GoodsService;
import com.shangyueshang.service.PlatformSpendService;
import com.shangyueshang.thread.ExecutorType;
import com.shangyueshang.utils.MyPageHelper;
import com.shangyueshang.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * @author: JokeyZheng
 * @email: zhengjingfeng@ruqimobility.com
 * @created: 2022/1/19 14:55
 * @version: v1.0.0
 */
@Slf4j
@Component
public class RevenueService {
	
	
	@Resource
	private OrderMapper orderMapper;
	
	@Resource
	private GoodsService goodsService;
	
	@Resource
	private RevenueMapper revenueMapper;
	
	@Resource
	private GoodsRevenueService goodsRevenueService;
	
	@Resource
	private PlatformSpendService platformSpendService;
	
	@Resource(name = ExecutorType.defaultExecutor)
	private Executor myExecutor;
	
	@Resource
	private ProfitExecuteLogMapper profitExecuteLogMapper;
	
	
	/**
	 * 在线计算
	 */
	private final static Set<String> ONLINE_COUNTING_SET = Collections.synchronizedSet(new HashSet<>(4));
	
	/**
	 * 执行结算逻辑
	 *
	 * <p>
	 * 统计过程中关于退款退货的逻辑讲解：
	 * 1、在下单后 取消订单，属于Refund;
	 * 2、在用户收到商品后，由于各种原因退货，也属于Refund，这种请况涉及到周期比较长远, 2~90天不等;
	 * 基于上述两点，我们在统计商品退款订单时，以退回日期(return_date)在交易日期(trade_date)范围内计算，
	 * </p>
	 *
	 * @param req
	 * @return
	 */
	@SuppressWarnings("all")
	public synchronized HttpResult<String> save(AccountingDTO req) {
		String shop = req.getShop();
		YearMonth tradeMonth = req.getTradeMonth();
		LocalDate toTradeDate = req.tradeMonthEndDate();
		LocalDate fromTradeDate = req.tradeMonthStartDate();
		BigDecimal exchangeRate = req.getExchangeRate();
		
		String key = shop + tradeMonth.toString();
		if (ONLINE_COUNTING_SET.contains(key)) {
			return HttpResult.ok("后台正在努力计算哦，请稍后刷新.....");
		}
		ONLINE_COUNTING_SET.add(key);
		
		//TODO 发送消息通知  String username = SecurityUtil.getUsername();
		
		myExecutor.execute(() -> {
			try {
				//统计商品销售毛收入
				List<GoodsRevenue> revenues = orderMapper.countSales(shop, fromTradeDate, toTradeDate);
				
				//统计商品采购/头程/包装成本
				Map<String, ShopSkuDTO> shopSkuCostMap = goodsService.queryByShop(shop);
				
				//统计周期内商品退款
				Map<String, GoodsRevenue> refundGoodsMap =
						orderMapper.countRefund(shop, fromTradeDate, toTradeDate)
								.stream()
								.collect(
										Collectors.toMap(
												k -> k.shopSkuKey() + "-" + k.getTradeDate(),
												v -> v
										)
								);
				
				for (GoodsRevenue goods : revenues) {
					try {
						String shopSkuTradeDateKey = goods.shopSkuKey() + "-" + goods.getTradeDate();
						//设置退款信息
						GoodsRevenue refundGoods = refundGoodsMap.get(shopSkuTradeDateKey);
						if (refundGoods != null) {
							goods.setReturnQuantity(refundGoods.getReturnQuantity());
							goods.setReturnFee(refundGoods.getReturnFee().multiply(exchangeRate));
							//计算完移除
							refundGoodsMap.remove(shopSkuTradeDateKey);
						}
						ShopSkuDTO shopSkuCost = shopSkuCostMap.get(goods.shopSkuKey());
						if (shopSkuCost != null) {
							BigDecimal quantity = new BigDecimal(goods.getSaleQuantity() - Optional.ofNullable(goods.getReturnQuantity()).orElse(0));
							goods.setHeadwayFee(BigDecimal.ZERO.subtract(shopSkuCost.getHeadwayFee().multiply(quantity)));
							goods.setPackFee(BigDecimal.ZERO.subtract(shopSkuCost.getPackCost().multiply(quantity)));
							goods.setPurchaseFee(BigDecimal.ZERO.subtract(shopSkuCost.getPurchaseCost().multiply(quantity)));
							goods.setName(shopSkuCost.getName());
							goods.setGoodsType(shopSkuCost.getGoodsType());
						} else {
							//TODO 发送消息通知
							log.warn("sku[{}] not set product info", goods.shopSkuKey());
						}
						goods.setProductSales(goods.getProductSales().multiply(exchangeRate));
						goods.setSaleRevenue(goods.getSaleRevenue().multiply(exchangeRate));
						goods.setSellingFee(goods.getSellingFee().multiply(exchangeRate));
						goods.setFbaFee(goods.getFbaFee().multiply(exchangeRate));
						GoodsRevenue grOne = goodsRevenueService.getByShopSkuTradeDate(goods.getShop(), goods.getSku(), goods.getTradeDate());
						if (grOne != null) {
							goods.setId(grOne.getId());
						} else {
							goods.setCreator(SecurityUtil.getUsername());
						}
						goods.setLastUpdator(SecurityUtil.getUsername());
						goodsRevenueService.saveOrUpdate(goods);
					} catch (Exception e) {
						log.error(goods.toString());
						throw e;
					}
				}
				//计算退款Map是否还存在数据，因为有些退款订单对应的交易日期是在N个月前的某一天
				if (!refundGoodsMap.isEmpty()) {
					refundGoodsMap.forEach((shopSkuTradeDate, goodsRevenue) -> {
						GoodsRevenue grOne =
								goodsRevenueService.getByShopSkuTradeDate(
										goodsRevenue.getShop(), goodsRevenue.getSku(), goodsRevenue.getTradeDate()
								);
						if (grOne == null) {
							grOne = new GoodsRevenue();
							grOne.setSku(goodsRevenue.getSku());
							grOne.setShop(goodsRevenue.getShop());
							grOne.setTradeDate(goodsRevenue.getTradeDate());
							grOne.setCreator(SecurityUtil.getUsername());
						}
						grOne.setReturnQuantity(goodsRevenue.getReturnQuantity());
						grOne.setReturnFee(goodsRevenue.getReturnFee().multiply(exchangeRate));
						grOne.setLastUpdator(SecurityUtil.getUsername());
						goodsRevenueService.saveOrUpdate(grOne);
					});
				}
				
				//计算平台费用
				List<KeyValDecimal> feeList = orderMapper.countFee(shop, fromTradeDate, toTradeDate, OrderType.feeTypeString());
				if (!feeList.isEmpty()) {
					Map<String, BigDecimal> feeMap = feeList.stream().collect(Collectors.toMap(KeyValDecimal::getKey, KeyValDecimal::getVal));
					PlatformSpend platformSpend = new PlatformSpend();
					platformSpend.setShop(shop);
					platformSpend.setTradeMonth(req.getTradeMonth());
					//有些站点使用deal fee， 有些站点使用lightning deal fee
					platformSpend.setDealFee(
							feeMap.getOrDefault(OrderType.DEAL_FEE.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setDealFee(
							platformSpend.getDealFee().add(
									feeMap.getOrDefault(OrderType.Lightning_DEAL_FEE.type, BigDecimal.ZERO).multiply(exchangeRate)
							)
					);
					platformSpend.setCouponRedemptionFee(
							feeMap.getOrDefault(OrderType.Coupon_Redemption_Fee.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setFbaInboundPlacementServiceFee(
							feeMap.getOrDefault(OrderType.FBA_Inbound_Placement_Service_Fee.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setInboundDefectFee(
							feeMap.getOrDefault(OrderType.Inbound_Defect_Fee.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setAdjustmentFee(
							feeMap.getOrDefault(OrderType.Adjustment.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setAdjustmentCompensate(
							feeMap.getOrDefault(OrderType.FEE_ADJUSTMENT.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setAdvertiseFee(
							feeMap.getOrDefault(OrderType.Cost_of_Advertising.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setSubscriptionFee(
							feeMap.getOrDefault(OrderType.Subscription.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setEarlyReviewerFee(
							feeMap.getOrDefault(OrderType.VINE_ENROLLMENT_FEE.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setFbaInventoryFee(
							feeMap.getOrDefault(OrderType.FBA_INVENTORY_FEE.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setDebtFee(
							feeMap.getOrDefault(OrderType.DEBT.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setAwdFee(
							feeMap.getOrDefault(OrderType.AWD_Fees.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					platformSpend.setSendShippingCharge(
							feeMap.getOrDefault(OrderType.Send_Shipping_Charge.type, BigDecimal.ZERO).multiply(exchangeRate)
					);
					PlatformSpend psOne = platformSpendService.getByShopAndMonth(shop, req.getTradeMonth());
					if (Objects.nonNull(psOne)) {
						platformSpend.setId(psOne.getId());
					} else {
						platformSpend.setCreator(SecurityUtil.getUsername());
					}
					platformSpend.setLastUpdator(SecurityUtil.getUsername());
					platformSpendService.saveOrUpdate(platformSpend);
				}
				
				//记录操作日志
				ProfitExecuteLog executeLog = new ProfitExecuteLog();
				executeLog.setShop(shop);
				executeLog.setTradeMonth(tradeMonth);
				executeLog.setExchangeRate(exchangeRate);
				executeLog.setCreateTime(LocalDateTime.now());
				executeLog.setCreator(SecurityUtil.getUsername());
				executeLog.setLastUpdateTime(LocalDateTime.now());
				executeLog.setLastUpdator(SecurityUtil.getUsername());
				profitExecuteLogMapper.insert(executeLog);
			} catch (Exception e) {
				//todo 发送消息通知
				log.error("统计执行出错：", e);
			} finally {
				ONLINE_COUNTING_SET.remove(key);
			}
		});
		return HttpResult.ok("正在努力计算哦，请稍后刷新.....");
	}
	
	/**
	 * 从店铺维度获取营收情况
	 *
	 * @param pageReq
	 * @return
	 */
	public PageResult<ShopRevenue> shopRevenueFindPage(PageReq pageReq) {
		Map<String, Object> lineParams = pageReq.getLineParams();
		PageInfo<ShopRevenue> pageInfo =
				PageHelper.startPage(
						pageReq.getPageNum(),
						pageReq.getPageSize()
				).doSelectPageInfo(
						() -> revenueMapper.queryShopRevenue(lineParams)
				);
		return MyPageHelper.getPageResult(pageInfo);
	}
	
	/**
	 * 从公司维度获取营收情况
	 *
	 * @param pageReq
	 * @return
	 */
	public PageResult<CompanyRevenue> companyRevenueFindPage(PageReq pageReq) {
		Map<String, Object> lineParams = pageReq.getLineParams();
		PageInfo<CompanyRevenue> pageInfo =
				PageHelper.startPage(
						pageReq.getPageNum(),
						pageReq.getPageSize()
				).doSelectPageInfo(
						() -> revenueMapper.queryCompanyRevenue(lineParams)
				);
		return MyPageHelper.getPageResult(pageInfo);
	}
}
